////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __WN_SCRIPTING_TYPE_MANAGER_H__
#define __WN_SCRIPTING_TYPE_MANAGER_H__

#include "WNCore/inc/WNTypes.h"
#include "WNConcurrency/inc/WNRecursiveMutex.h"
#include "WNScripting/inc/WNScriptingConfig.h"
#include "WNScripting/inc/WNIdentifierMap.h"
#include "WNScripting/inc/WNTypeElement.h"
#include "WNScripting/inc/WNFunction.h"

#include <list>

namespace llvm {
    class Value;
    class ConstantFolder;
    class GlobalVariable;
    class IRBuilderBase;
    class Type;
};
namespace WNScripting {
    struct WNScriptVariable;
    struct WNScriptingEngineData;
    //TODO: Major cleanup on this file
    //      it is pretty messy, need to move all of the "type" stuff at the very least
    //      into it's own header
    //TODO: Should probably hide the hash_map hashing/comparator somewhere else 
    //      (probably a platform specific scripting header (hash_map is non standard)
    //      (Worst case pass in VOID* and cast, do not like this.. figure out better way)

    enum WNCastAllowance {
        eWNCastOk,
        eWNCastLossPrecision,
        eWNCastFail,
        eWNCastInvalidArithmetic
    };

    enum WNStaticArithmetic {
        eWNStaticAdd,
        eWNStaticSubtract,
        eWNStaticMultiply,
        eWNStaticMod,
        eWNStaticDivide,
        eWNStaticEquality,
        eWNStaticBrackets,
        eWNStaticAssign,
        eWNStaticPlusEqual,
        eWNStaticMinusEqual,
        eWNStaticTimesEqual,
        eWNStaticDivideEqual,
        eWNStaticModEqual,
        eWNStaticERROR
    };

    //This must line up with the sIntPredicates/sFloatPredicates
    enum WNStaticPredicate {
        eWNEqual = 0,
        eWNNotEqual = 1,
        eWNLessThan = 2,
        eWNGreaterThan = 3,
        eWNLessThanOrEqual = 4,
        eWNGreaterThanOrEqual = 5,
        eWNInvalidPredicate = 255
    };

    struct WNNativeElementType {
        WNTypeElement mScriptType;
        const WN_CHAR* mUserType;
        WN_CHAR mCharacterIdentifier;
        WN_BOOL mArray;
    };

    typedef WN_VOID (*WNStaticArithmeticFunction)(WN_CHAR*, WN_CHAR*, WNStaticArithmetic, WNStaticPredicate, WNTypeElement&, WNCastAllowance&, WN_CHAR*);
    typedef WNCastAllowance (*WNVerifyDynamicArithmetic)(WNStaticArithmetic, const WNTypeElement&, const WNTypeElement&, WNTypeElement&);
    typedef WN_BOOL  (*WNStaticTypeCheckFunction)(WN_CHAR*);
    typedef llvm::Value* (*WNGenerateLLVMOperation)(llvm::Value*, llvm::Value*, const WNTypeElement&, WNStaticArithmetic, WNStaticPredicate, WNScriptingEngineData&);
    typedef llvm::Value* (*WNGenerateLLVMCast)(llvm::Value* val, llvm::IRBuilderBase&);
    typedef llvm::Value* (*WNCreateConstantFunction)(WNScriptingEngineData&, WN_CHAR* constValue);
    typedef llvm::Value* (*WNCreateStore)(WNScriptingEngineData&, llvm::Value*, llvm::Value*, bool initial,  WNStaticArithmetic);
    typedef llvm::Value* (*WNGetCopySize)(WNScriptingEngineData&, llvm::Value* val);

    struct WNArithmeticStruct {
        WNTypeElement mLHS;
        WNTypeElement mRHS;
        WNStaticArithmeticFunction mStaticArithmetic;
        WNVerifyDynamicArithmetic  mVerifyDynamicArithmetic;
        WNGenerateLLVMOperation    mGenerateLLVMOperation;
        WNGenerateLLVMCast         mGenerateCast;
    };

    struct WNTypeCheckStruct {
        WNTypeElement type;
        WNStaticTypeCheckFunction mStaticTypeCheck;
        WNCreateConstantFunction  mCreateConstant;
        WNCreateStore             mCreateStore;
        WNGetCopySize             mGetCopySize;
    };

    struct __WNTypeLess {
        WN_BOOL operator()(WNTypeElement _key1, WNTypeElement _key2) const {
            return (((*_key1).mUserTypeName < (*_key2).mUserTypeName) ||
                    ((*_key1).mUserTypeName == (*_key2).mUserTypeName &&
                     (*_key1).mSubType < (*_key2).mSubType));
        }
    };

    typedef std::list<WNTypeElement> WNArrayHashMap;
    typedef std::list<WNArithmeticStruct*> WNArithmeticStructList;
    typedef std::list<WNTypeCheckStruct*> WNTypeCheckStructList;
    typedef std::list<WNNativeElementType*> WNNativeElementList;
    typedef std::list<WNTypeElement> WNTypeElementList;

    const WNTypeElement WNInvalidType = WN_NULL;

    class WNTypeManager {
    public:
        WNTypeManager(WNIdentifierMap& _identifierMap);
        WN_BOOL TypeExists(WNTypeElement _type);
        WNTypeElement GetType(WNVariableType _type, WNIdentifierElement _name = WNInvalidIdentifier);
        WNTypeElement AddUserType(WNIdentifierElement _type);
        WNIdentifierElement GetUserTypeName(WNTypeElement _userType);
        WNTypeElement GetUserTypeByName(WNIdentifierElement _name);
        WNTypeElement GetArrayType(WNTypeElement _arrayType);
        WN_VOID AddStructBody(WNTypeElement _elem, const std::vector<WNTypeElement>& _typeList, const std::vector<WNIdentifierElement>& _argumentList);
        WNTypeElement RegisterStructType(WNIdentifierElement _structName);

        //TODO: Look at the system, and see if this is really the case (that we get and only use once)
        //      Since if we ever use twice, we can save the lookup time by keeping it like this
        WNStaticArithmeticFunction GetStaticArithmeticFunction(WNTypeElement _arg1, WNTypeElement _arg2);
        WNStaticTypeCheckFunction GetStaticTypeCheckFunction(WNTypeElement _element);
        WNVerifyDynamicArithmetic GetDynamicArithmeticVerification(WNTypeElement _to, WNTypeElement _from);
        WNGenerateLLVMOperation   GetLLVMOperationGenerator(WNTypeElement _arg1, WNTypeElement _arg2);
        WNCreateConstantFunction  GetLLVMCreateConstant(WNTypeElement _arg);
        WNCreateStore       GetLLVMStoreInst(WNTypeElement _arg);
        WNGenerateLLVMCast  GetLLVMCastGenerator(WNTypeElement _to, WNTypeElement _from);
        WNGetCopySize       GetLLVMCopySize(WNTypeElement _arg);
        WNNativeElementType* GetNativeFromType(WNTypeElement _type);
        WNNativeElementType* GetNativeFromWNType(WNIdentifierElement _nativeType);
        WNNativeElementType* GetNativeArrayFromWNType(WNIdentifierElement _nativeType);
        llvm::Value* GenerateLLVMAllocation(WN_BOOL _heap, WNScriptingEngineData &_data, llvm::Type* _type, llvm::Value* _numElems = WN_NULL);

        llvm::Type* GetIntPtrType() const;
        llvm::Type* GetLLVMType(WNTypeElement _type) const;
        WN_BOOL IsArrayType(WNTypeElement _type) const;
        WN_BOOL IsReferenceType(WNTypeElement _type) const;
        WN_BOOL IsStructType(WNTypeElement _structType) const;
        WNTypeElement GetSubType(WNTypeElement _type) const;
        WN_VOID Initialize(const WNScriptingEngineData& _initializationData);
        WN_BOOL GetStructValueLocation(WNTypeElement _elem, WNIdentifierElement _ident, WNTypeElement& _retElem, WN_SIZE_T& _outVal);
        llvm::Value* CreateAllocation(WNScriptingEngineData &_data, WNTypeElement _type, const std::vector<llvm::Value*>& _amounts, WN_BOOL _heap, WN_BOOL _suppressLastCreation);

    
        llvm::Value* CreateConstAtomicAdd(WNScriptingEngineData& _data, llvm::Value* _ptr, WN_SIZE_T _addAmount);
        llvm::Value* CreateConstAtomicSubtract(WNScriptingEngineData& data, llvm::Value* _ptr, WN_SIZE_T subAmount);
        std::vector<llvm::GlobalVariable*> mGlobalVariables;
        llvm::StructType* mInternalStringStruct;

    private:
        llvm::Value* RecurseCreateArrayAlloc(WNScriptingEngineData& _data, WNTypeElement _type, const std::vector<llvm::Value*>& _amounts, WN_BOOL _heap, WN_SIZE_T _arrayIndex, WN_BOOL _suppressLastCreation);

        WNArithmeticStruct* GetStaticArithmeticStruct(WNTypeElement, WNTypeElement);
        WNTypeCheckStruct*  GetStaticTypeCheckStruct(WNTypeElement);
        WNTypeElementList mTypeMap;
        WNArrayHashMap mArrayHashMap;
        WNTypeCheckStructList mTypeCheckStructList;
        WNArithmeticStructList mArithmeticStructList;
        WNNativeElementList mNativeTypeList;
        WN_BOOL mInitializing;
        WNIdentifierMap& mIdentifierMap;
        ///LLVM SPECIFIC STUFF
        llvm::Type* mPointerInteger;

        #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
            WNConcurrency::WNRecursiveMutex mTypeManagerLock;
            WNConcurrency::WNRecursiveMutex mTypeCheckLock;
            WNConcurrency::WNRecursiveMutex mArithmeticLock;
        #endif
    };
}


template <WNScripting::WNTypeElement>
WN_INLINE WN_BOOL WNIsValid( WNScripting::WNTypeElement _value ) {
    return ( _value != WNScripting::WNInvalidType );
}
#endif // __WN_SCRIPTING_TYPE_MANAGER_H__
